﻿using DBUtil.Generators;
using DotNetCommon;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Test.SqlServer
{
    /// <summary>
    /// 测试Id和流水号生成,基于数据库缓存
    /// </summary>
    [TestClass]
    public sealed partial class GeneratorTests : TestBase
    {
        #region Id生成
        [TestCategory("Id生成-基于数据库缓存")]
        [TestMethod]
        public void NewId_DBCacheTest()
        {
            using (db.OpenLongConnectOnce())
            {
                //准备测试表
                if (db.JudgeTableExist("test"))
                {
                    db.Manage.DropTable("test");
                }
                db.ExecuteSql(@"create table test(
id int primary key,
name varchar(50)
)");
                if (db.JudgeProcedureExist(db.Settings.DBCacheGeneratorIdProcedureName))
                {
                    db.ExecuteSql($"drop proc {db.Settings.DBCacheGeneratorIdProcedureName}");
                }
                if (db.JudgeTableExist(db.Settings.DBCacheGeneratorLogTableName))
                {
                    db.Manage.DropTable(db.Settings.DBCacheGeneratorLogTableName);
                }
                db.ResetId("test", "id");
                //单个id生成
                long id = db.NewId("test", "id");
                Assert.IsTrue(id == 1);
                id = db.NewId("test", "id");
                Assert.IsTrue(id == 2);
                //根据表中已有的id,继续生成
                var dic = new Dictionary<string, object>();
                dic.Add("id", id);
                dic.Add("name", "xiaoming" + id);
                db.Insert("test", dic);
                db.ResetId("test", "id");
                id = db.NewId("test", "id");
                Assert.IsTrue(id == 3);
                //根据缓存批量生成id
                var ids = db.NewIds("test", "id", 5);
                var expect = new List<long>() { 4, 5, 6, 7, 8 };
                var expectStr = string.Join(",", expect);
                var actStr = string.Join(",", ids);
                Assert.IsTrue(expectStr == actStr);
                //根据表中已有的id,继续批量生成
                db.ResetId("test", "id");
                ids = db.NewIds("test", "id", 5);
                expect = new List<long>() { 3, 4, 5, 6, 7 };
                expectStr = string.Join(",", expect);
                actStr = string.Join(",", ids);
                Assert.IsTrue(expectStr == actStr);
                //直接从缓存中生成
                db.Manage.TruncateTable("dbo", "test");
                db.ResetId("test", "id");
                ids = db.NewIds("test", "id", 5);
                expect = new List<long>() { 1, 2, 3, 4, 5 };
                expectStr = string.Join(",", expect);
                actStr = string.Join(",", ids);
                Assert.IsTrue(expectStr == actStr);
            };
        }

        [TestCategory("Id生成-基于数据库缓存-并发测试")]
        [TestMethod]
        public void NewIdConcurrency_DBCacheTest()
        {
            //准备测试表
            if (db.JudgeTableExist("test"))
            {
                db.Manage.DropTable("test");
            }
            db.ExecuteSql(@"create table test(
id int primary key,
name varchar(50)
)");
            if (db.JudgeProcedureExist(db.Settings.DBCacheGeneratorIdProcedureName))
            {
                db.ExecuteSql($"drop proc {db.Settings.DBCacheGeneratorIdProcedureName}");
            }
            if (db.JudgeTableExist(db.Settings.DBCacheGeneratorLogTableName))
            {
                db.Manage.DropTable(db.Settings.DBCacheGeneratorLogTableName);
            }
            db.ResetId("test", "id");
            db.Manage.TruncateTable("dbo", "test");
            var tasks = new List<Task>();
            var idres = new System.Collections.Concurrent.ConcurrentQueue<(long id, int threadid)>();
            for (int i = 0; i < 100; i++)
            {
                tasks.Add(Task.Factory.StartNew(() =>
                {
                    try
                    {
                        var db2 = db.CreateNewDB();
                        using (db2.OpenLongConnectOnce())
                        {
                            for (int j = 0; j < 100; j++)
                            {
                                var idg = db2.NewId("test", "id");
                                idres.Enqueue((idg, Thread.CurrentThread.ManagedThreadId));
                            }
                        };
                    }
                    catch (Exception ex)
                    {
                        Debug.Write(ex?.Message + ex?.StackTrace);
                        Debug.Fail(ex?.Message, ex?.StackTrace);
                    }
                }, TaskCreationOptions.LongRunning));
            }
            Task.WaitAll(tasks.ToArray());
            var list = idres.ToList();
            list = list.OrderByDescending(t => t.id).ToList();
            var expactCount = 100 * 100;
            Assert.IsTrue(list.LastOrDefault().id == 1);
            Assert.IsTrue(list.FirstOrDefault().id == expactCount);
            Assert.IsTrue(list.Count == expactCount);
        }
        #endregion

        #region SNO生成
        [TestCategory("测试SNO生成-基于数据库缓存")]
        [TestMethod]
        public void NewSNO_DBCacheTest()
        {
            using (db.OpenLongConnectOnce())
            {
                //准备测试表
                if (db.JudgeTableExist("test"))
                {
                    db.Manage.DropTable("test");
                }
                db.ExecuteSql(@"create table test(
id int primary key,
sno varchar(50),
name varchar(50)
)");
                if (db.JudgeProcedureExist(db.Settings.DBCacheGeneratorSNOProcedureName))
                {
                    db.ExecuteSql($"drop proc {db.Settings.DBCacheGeneratorSNOProcedureName}");
                }
                if (db.JudgeTableExist(db.Settings.DBCacheGeneratorLogTableName))
                {
                    db.Manage.DropTable(db.Settings.DBCacheGeneratorLogTableName);
                }
                db.ResetSNO("test", "sno");
                //单个生成
                var sno = db.NewSNO("test", "sno", SerialFormat.CreateFast("SNO"));
                Assert.IsTrue(sno == "SNO" + DateTime.Now.ToString("yyyyMMdd") + "000001");
                sno = db.NewSNO("test", "sno", SerialFormat.CreateFast("SNO"));
                Assert.IsTrue(sno == "SNO" + DateTime.Now.ToString("yyyyMMdd") + "000002");
                //根据表中已有的sno,继续生成
                var dic = new Dictionary<string, object>();
                var id = db.NewId("test", "id");
                dic.Add("id", id);
                dic.Add("name", "xiaoming" + id);
                dic.Add("sno", sno);
                db.Insert("test", dic);
                db.ResetSNO("test", "sno");
                sno = db.NewSNO("test", "sno", SerialFormat.CreateFast("SNO"));
                Assert.IsTrue(sno == "SNO" + DateTime.Now.ToString("yyyyMMdd") + "000003");
                //根据缓存批量生成sno
                var snos = db.NewSNOs("test", "sno", SerialFormat.CreateFast("SNO"), 5);
                var expect = new List<string>() { $"SNO{DateTime.Now.ToString("yyyyMMdd")}000004", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000005", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000006", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000007", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000008" };
                var expectStr = string.Join(",", expect);
                var actStr = string.Join(",", snos);
                Assert.IsTrue(expectStr == actStr);
                //根据表中已有的id,继续批量生成
                db.ResetSNO("test", "sno");
                snos = db.NewSNOs("test", "sno", SerialFormat.CreateFast("SNO"), 5);
                expect = new List<string>() { $"SNO{DateTime.Now.ToString("yyyyMMdd")}000003", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000004", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000005", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000006", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000007" };
                expectStr = string.Join(",", expect);
                actStr = string.Join(",", snos);
                Assert.IsTrue(expectStr == actStr);
                //直接从缓存中生成
                db.Manage.TruncateTable("dbo", "test");
                db.ResetSNO("test", "sno");
                snos = db.NewSNOs("test", "sno", SerialFormat.CreateFast("SNO"), 5);
                expect = new List<string>() { $"SNO{DateTime.Now.ToString("yyyyMMdd")}000001", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000002", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000003", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000004", $"SNO{DateTime.Now.ToString("yyyyMMdd")}000005" };
                expectStr = string.Join(",", expect);
                actStr = string.Join(",", snos);
                Assert.IsTrue(expectStr == actStr);
            };
        }

        [TestCategory("测试SNO生成-基于数据库缓存-并发测试")]
        [TestMethod]
        public void NewSNO_DBCacheConcurrencyTest()
        {
            //准备测试表
            if (db.JudgeTableExist("test"))
            {
                db.Manage.DropTable("test");
            }
            db.ExecuteSql(@"create table test(
id int primary key,
sno varchar(50),
name varchar(50)
)");
            if (db.JudgeProcedureExist(db.Settings.DBCacheGeneratorSNOProcedureName))
            {
                db.ExecuteSql($"drop proc {db.Settings.DBCacheGeneratorSNOProcedureName}");
            }
            if (db.JudgeTableExist(db.Settings.DBCacheGeneratorLogTableName))
            {
                db.Manage.DropTable(db.Settings.DBCacheGeneratorLogTableName);
            }
            db.ResetSNO("test", "sno");
            db.Manage.TruncateTable("dbo", "test");
            var tasks = new List<Task>();
            var snores = new System.Collections.Concurrent.ConcurrentQueue<(string sno, int threadid)>();
            for (int i = 0; i < 100; i++)
            {
                tasks.Add(Task.Factory.StartNew(() =>
                {
                    try
                    {
                        var db2 = db.CreateNewDB();
                        using (db2.OpenLongConnectOnce())
                        {
                            for (int j = 0; j < 100; j++)
                            {
                                var sno = db2.NewSNO("test", "sno", SerialFormat.CreateFast("SNO"));
                                snores.Enqueue((sno, Thread.CurrentThread.ManagedThreadId));
                            }
                        };
                    }
                    catch (Exception ex)
                    {
                        Debug.Write(ex?.Message + ex?.StackTrace);
                        Debug.Fail(ex?.Message, ex?.StackTrace);
                    }
                }, TaskCreationOptions.LongRunning));
            }
            Task.WaitAll(tasks.ToArray());
            var list = snores.ToList();
            list = list.OrderByDescending(t => t.sno).ToList();
            var expactCount = 100 * 100;
            Assert.IsTrue(list.FirstOrDefault().sno == $"SNO{DateTime.Now.ToString("yyyyMMdd")}{expactCount.ToString().PadLeft(6, '0')}");
            Assert.IsTrue(list.Count == expactCount);
        }
        #endregion
    }
}
